package mjava.gc;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

import mjava.lib.Attribut;
import mjava.lib.Constructeur;
import mjava.lib.DPARAM;
import mjava.lib.DPARAMS;
import mjava.lib.DTYPE;
import mjava.lib.INFO;
import mjava.lib.Methode;
import mjava.lib.TdC;
import mjava.lib.TdS_itf;

public class TAM extends AbstractMachine {

	public static final int INT_TAILLE = 1;
	public static final int BOOL_TAILLE = 1;
	public static final int REFERENCE_TAILLE = 1;
	public static final int ERROR_TAILLE = 1;
	
	public static final String LB = "[LB]";
	public static final String SB = "[SB]";
	public static final String HB = "[HB]";
	
	public static final String ST = "[ST]";
	public static final String HT = "[HT]";
	
	//label counter
	private static int count = 0; 
	
	// Character code line starter 
	public final static String nl = "\n";
	
	public String currentClassName;

	// Current fileName 
	private String fileName;
	
	public String getSuffixe() {
		return "tam";
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeAddInt()
	 */
	@Override
	public String codeAddInt() {
		return "SUBR IAdd" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeAffect(java.lang.String, int, int)
	 */
	@Override
	public String codeAffect(String reg, int depl, int taille) {
		return "STORE (" + taille + ") " + depl + "[" + reg + "]" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeAnd()
	 */
	@Override
	public String codeAnd() {
		return "SUBR BAnd" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeBloc(java.lang.String, mjava.lib.TdS)
	 */
	@Override
	public String codeBloc(String insts, TdS_itf tds) {
		String res = insts + nl;
		return res + "POP (0) " + tds.getTaille() + nl; 
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeConstructorCall(mjava.lib.Constructeur, mjava.lib.DPARAMS, mjava.lib.TdC)
	 */
	@Override
	public String codeConstructorCall(Constructeur c, String args, String name) {
		String res = nl;
		res += args;
		res += "PUSH 1" + nl; // this
		res += "CALL " + LB + " " + c.getConstructorLable(name); // using LB as dynamique link
		return res;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeConstructorDef(mjava.lib.Constructeur, java.lang.String)
	 */
	@Override
	public String codeConstructorDef(Constructeur c, String bloc) {
		
		String res = nl + c.getConstructorLable(this.currentClassName);
		res += ";code.bloc.start" + nl ;
		
		/*DPARAMS params = c.getParams();
		Vector<DPARAM> paramsList = params.getParamList();
		DPARAM param = null;
		for(int i = 0; i < paramsList.size(); i++) {
			param = paramsList.get(i);
			res += "LOAD (" + param.getParamTaille() + ") " + param.getParamDepl() + LB + nl;
		}*/
		
		res += bloc + ";code.bloc.end" + nl;
		
		// Constructor has no result, so POP size 0; then POP then size of args 
		res += "RETURN (0) " + c.getParams().getTaille() + nl;
		
		return res;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeDeclVar(mjava.lib.Attribut, java.lang.String)
	 */
	@Override
	public String codeDeclVar(INFO attr, String e, boolean isVal) {
		
		String res = "PUSH " + attr.getTaille() + nl; 
		res += e;
		if(!isVal) res += "LOADI (1)" + nl;
		res += "STORE (" + attr.getType().getTaille() + ") " + attr.getDeplacement() + LB + nl; 
		return res;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeDeclVar(mjava.lib.Attribut)
	 */
	@Override
	public String codeDeclVar(INFO attr) {
		return "PUSH " + attr.getTaille() + nl; 
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeDivInt()
	 */
	@Override
	public String codeDivInt() {
		return "SUBR IDiv" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeEqualInt()
	 */
	@Override
	public String codeEqualInt() {
		return "SUBR IEq" + nl; 
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeFalse()
	 */
	@Override
	public String codeFalse() {
		return "LOADL 0" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeIf(java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public String codeIf(String condition, String blocTrue, String blocFalse) {
		
		String etiqElse = this.codeNewLable("LBL_ELSE");
		String etiqEndif = this.codeNewLable("LBL_ENDIF");
		String res = nl;

		res = condition;
		if(!blocFalse.equals("")) {
			
			res += nl + "JUMPIF (0) " + etiqElse + nl;
			res += blocTrue;
			res += nl + "JUMP " + etiqEndif + nl;
			res += etiqElse + nl;
			res += blocFalse;
			res += etiqEndif + nl;
		} else {
			res += nl + "JUMPIF (0) " + etiqEndif + nl;
			res += blocTrue;
			res += etiqEndif + nl;
		}
		
		return res;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeInt(int)
	 */
	@Override
	public String codeInt(String i) {
		return "LOADL " + i + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeLessEqualInt()
	 */
	@Override
	public String codeLessEqualInt() {
		return "SUBR ILeq" + nl; 
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeLessInt()
	 */
	@Override
	public String codeLessInt() {
		return "SUBR ILss" + nl; 
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeLoadVar(mjava.lib.Attribut)
	 */
	@Override
	public String codeLoadVar(INFO i, boolean global) {
		String res = nl;
		
		res = "LOAD (" + i.getTaille() + ") " + i.getDeplacement();
		if (global) {
			res += SB + nl;
		} else {
			res += LB + nl;
		}
		return res;
	}
	
	@Override
	public String codeLoadVarAddr(INFO i){
		return "LOADA " + i.getDeplacement() + LB + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeMethodCall(mjava.lib.Methode, mjava.lib.DPARAMS, mjava.lib.TdS)
	 */
	@Override
	public String codeMethodCall(Methode m, DPARAMS params, String args) {
		String res = nl;
		
		// res += ags + nl;
		
		/*Vector<DPARAM> paramsList = params.getParamList(); 
		DPARAM param = null;
		int tmpTaille = 0;
		
		for(int i = 0; i < paramsList.size(); i++) {
			param = paramsList.get(i);
		}*/
		
		res += "PUSH " + params.getTaille() + nl;
		res += "LOAD -" + params.getTaille() + LB + nl;
		res += "CALL (LB) " + m.getMethodeLable() + nl;
		
		/*res += "LOAD (1) -" + params.getTaille() + ST + nl;
		res += "POP (" + params.getTaille() + ")";
		res += "LOADL "+ m.getDeplacement();
		res += "SUBR IAdd" + nl;
		res += "CALLI" + nl;*/
		return res;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeMethodDef(mjava.lib.Methode, java.lang.String)
	 */
	@Override
	public String codeMethodDef(Methode m, String bloc) {
		String res = nl;
		
		res += m.getMethodeLable();
		
		// prepare the params
		DPARAMS params = m.getParams();
		Vector<DPARAM> paramsList = params.getParamList();
		DPARAM param = null;
		int tmpTaille = params.getTaille();
		for(int i = 0; i < paramsList.size(); i++) {
			param = paramsList.get(i);
			res += "LOAD -" + Integer.toString(tmpTaille) + LB + nl;
			tmpTaille -= param.getParamTaille();
		}
		
		res += bloc + nl;

		
		// bloc generate RETURN and POP
		/*if("void".equals(m.getType().getName())) {
			// if void, then no result to POP, just POP params
			res += "RETURN (0) "+ m.getParams().getTaille() + nl;
		} else {
			// if has a return type, then POP params and result
			res += "RETURN (" + m.getType().getTaille() + ") "+ m.getParams().getTaille() + nl;
		}*/
		
		return res;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeMinusInt()
	 */
	@Override
	public String codeMinusInt() {
		return "SUBR ISub" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeModInt()
	 */
	@Override
	public String codeModInt() {
		return "SUBR IMod" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeMoreEqualInt()
	 */
	@Override
	public String codeMoreEqualInt() {
		return "SUBR IGeq" + nl; 
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeMoreInt()
	 */
	@Override
	public String codeMoreInt() {
		return "SUBR IGtr" + nl; 
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeMultInt()
	 */
	@Override
	public String codeMultInt() {
		return "SUBR IMul" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeNegInt()
	 */
	@Override
	public String codeNegInt() {
		return "SUBR INeg" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeNewLable()
	 */
	@Override
	public String codeNewLable(String lable) {
		count++;
		return lable + "_" + Integer.toString(count);
		
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeNot()
	 */
	@Override
	public String codeNot() {
		return "SUBR BNeg" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeNotEqualInt()
	 */
	@Override
	public String codeNotEqualInt() {
		return "SUBR INeq" + nl; 
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeNull()
	 */
	@Override
	public String codeNull() {
		return nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeOr()
	 */
	@Override
	public String codeOr() {
		return "SUBR BOr" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeStr(java.lang.String)
	 */
	/*@Override
	public String codeStr(String s) {
		// TODO Auto-generated method stub
		return null;
	}*/

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeThis()
	 */
	@Override
	public String codeThis() {
		return "LOAD -1" + LB + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeTrue()
	 */
	@Override
	public String codeTrue() {
		return "LOADL 1" + nl;
	}

	/* (non-Javadoc)
	 * @see mjava.gc.AbstractMachine#codeWhile(java.lang.String, java.lang.String)
	 */
	@Override
	public String codeWhile(String condition, String bloc) {
		
		String etiqBeginWhile = this.codeNewLable("LBL_STARTWHILE");
		String etiqEndWhile = this.codeNewLable("LBL_ENDWHILE");
		String res = nl;
		
		res = etiqBeginWhile + nl + condition + nl +  "JUMPIF (0) " + etiqEndWhile + nl + 
			bloc + nl + "JUMP " + etiqBeginWhile + nl + etiqEndWhile + nl;
		
		return res;
	}

	/**
	 * @return the currentClassName
	 */
	public String getCurrentClassName() {
		return currentClassName;
	}

	/**
	 * @param currentClassName the currentClassName to set
	 */
	public void setCurrentClassName(String currentClassName) {
		this.currentClassName = currentClassName;
	}

	@Override
	public String codeLoadValue(int v) {
		return "LOADL " + Integer.toString(v) + nl;
	}

	@Override
	public String codeLoadValue(boolean b) {
		return "LOADL " + Integer.toString(b ? 1 : 0) + nl;
	}
	
	public String codeLoadValFromAddr(String expr, DTYPE t) {
		// POP a dada addr, then fetch a n words addr from that addr
		return expr + "LOADI ( " + t.getTaille() + ")" + nl;
	}

	@SuppressWarnings("unchecked")
	public String codeClassInfo(TdC tdc) {
		
		HashMap<String, Attribut> tda = tdc.getTdA();
		HashMap<String, Methode> tdm = tdc.getTdM();
		HashMap<String, Constructeur> tdConst = tdc.getTdConst();
		
		String res = nl;
				
		Methode mainMethode = tdc.checkMethod("main", "", this.currentClassName);
		if(mainMethode != null) {
			//res += "CALL (LB) "  + "DEFAULT8contr" ?;
			res += "CALL " + LB + " " + mainMethode.getMethodeLable() + nl; 
			res += "HALT" + nl;
		};
		
		res += "ACTIVE_METHOD_LBL " + nl;
		
		Iterator itm = tdm.keySet().iterator();
		Methode tmpMethode = null;
		while(itm.hasNext()) {
			tmpMethode = tdm.get((String)itm.next());
			res += "JUMP " + tmpMethode.getMethodeLable() + nl;
		}

		Iterator itc = tdConst.keySet().iterator();
		Constructeur tmpConstr = tdConst.get((String)itc.next());
		res += "LOADL " + tdc.getTaille() + nl;
		res += "SUBR MAlloc" + nl;									
		res += "LOADA " + tmpConstr.getConstructorLable(this.currentClassName) + nl;
		res += "LOAD (1) 3" + LB + nl;
		res += "STOREI (1)" + nl;
		res += "RETURN (1) 0" + nl;	
		
		while(itc.hasNext()) {
			tmpConstr = tdConst.get((String)itc.next());
			
			res += tmpConstr.getConstructorLable(this.currentClassName) + nl;
			res += "LOADL " + tdc.getTaille() + nl;
			res += "SUBR MAlloc" + nl;
			res += "LOADA " + tmpConstr.getConstructorLable(this.currentClassName) + nl;
			res += "LOAD (1) 3" + LB + nl;
			res += "STOREI (1)" + nl;
			res += "STORE (1) -1" + LB + nl;
			res += "JUMP " + tmpConstr.getConstructorLable(this.currentClassName) + nl; // JUMP out of the constr -> Corps ?
		}
		return res;
	}
	
	@Override
	public String codeNoAssignmentInst(String e, DTYPE t) {
		String res = e;
		res += "POP (0) " + t.getTaille() + "; pop the instruction if no assignement" + nl;
		
		return res;
	}

	@Override
	public String codeReturn(String e, DTYPE t, DPARAMS p) {
		String res = e;
		res += "RETURN (" + t.getTaille() + ") " + p.getTaille() + nl;
		return res;
	}

	@Override
	public String codeAssignmentInst(String n1, String n2, boolean isVal1, boolean isVal2, INFO i1, INFO i2) {
		String res = "";

		//e1 : left;   e2 : right
		res += n2;
		
		if(!isVal2) {
			
			//	load the pointed value into stack
			res += "LOADI (1)" + nl; 
			
			// -2 [ST] ??
			//res += "LOAD (1) -2 " + ST + nl;
			
			// affect the value to the specified address
			//res += "STOREI (" + t1.getTaille() + ") " + nl;
			
			// load the result into the stack
			//res += "LOADI (1) " + nl;
		} else {
			//res += "STORE (" + t1.getTaille() + ") " + dep + SB + nl;
		}
		
		if(!isVal1) {
			res += n1;
			res += "STOREI (" + i1.getTaille() + ") " + nl;
		} else {
			// search le deplacement de Var dans e1
			
			String base = SB;
			if(!i1.isGlobal()) base = LB;
			res += "STORE (" + i1.getTaille() + ") " + i1.getDeplacement() + base + nl;
		}
		
		return res;
	}

	@Override
	public void setFileName(String n) {
		this.fileName = n;
	}

	@Override
	public String getFileName() {
		return this.fileName;		
	}
}
